package mspbots.liongard.client.core.client;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.log4j.Log4j2;
import mspbots.liongard.common.QueryParams;
import mspbots.liongard.common.RabbitKeys;
import mspbots.liongard.common.ResultSync;
import mspbots.liongard.common.annotation.ApiException;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.Duration;

/**
 * com.mspbots.api.client.WiseClient
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/11/12
 */
@Log4j2
@Service
public class LiongardClient {

    private final WebClient webClient;
    private final RabbitMessagingTemplate rabbitMessagingTemplate;

    public LiongardClient(WebClient.Builder builder,
                          RabbitMessagingTemplate rabbitMessagingTemplate) {
        this.webClient = builder.defaultHeader("Accept", "application/json").build();
        this.rabbitMessagingTemplate = rabbitMessagingTemplate;
    }

    public Mono<Long> count(String path, QueryParams queryParams) {
        log.debug("Count uri: {}, Params; {}", path, queryParams);
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties clientProperties = ctx.get("properties");
            ResultSync resultSync = ctx.getOrDefault("resultSync", ResultSync.withDefault(path));
            assert resultSync != null;
            resultSync.setElapsedTime(System.currentTimeMillis());
            resultSync.setTenantId(clientProperties.getTenantId());
            resultSync.setParams(queryParams);
            return this.webClient.get()
                    .uri(uriBuilder -> uriBuilder.scheme("https").host(clientProperties.getEnvironment())
                            .path("/api/v1").path(path).queryParams(queryParams).build())
                    .headers(httpHeaders -> httpHeaders.set("X-ROAR-API-KEY",
                            HttpHeaders.encodeBasicAuth(clientProperties.getKey(), clientProperties.getSecret(),
                                    StandardCharsets.UTF_8)))
                    .retrieve()
                    .onStatus(HttpStatus::isError, clientResponse -> clientResponse.bodyToMono(JsonNode.class)
                            .map(errMsg -> new ApiException(clientResponse.statusCode(), errMsg)))
                    .bodyToMono(Long.class)
                    .onErrorResume(err -> {
                        this.handlerException(err, resultSync);
                        return Mono.error(err);
                    });
        });
    }

    public Flux<JsonNode> fetch(String path, QueryParams queryParams) {
        return Flux.empty();
    }

    public Mono<JsonNode> get(String path, QueryParams queryParams) {
        log.debug("Request uri: {}, Params; {}", path, queryParams);
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties clientProperties = ctx.get("properties");
            ResultSync resultSync = ctx.getOrDefault("resultSync", ResultSync.withDefault(path));
            assert resultSync != null;
            resultSync.setElapsedTime(System.currentTimeMillis());
            resultSync.setTenantId(clientProperties.getTenantId());
            resultSync.setParams(queryParams);
            return this.webClient.get()
                    .uri(uriBuilder -> uriBuilder.scheme("https").host(clientProperties.getEnvironment())
                            .path("/api/v1/").path(path).queryParams(queryParams).build())
                    .headers(httpHeaders -> httpHeaders.set("X-ROAR-API-KEY",
                            HttpHeaders.encodeBasicAuth(clientProperties.getKey(), clientProperties.getSecret(),
                                    StandardCharsets.UTF_8)))
                    .retrieve()
                    .onStatus(HttpStatus::isError, clientResponse -> clientResponse.bodyToMono(JsonNode.class)
                            .map(errMsg -> new ApiException(clientResponse.statusCode(), errMsg)))
                    .bodyToMono(JsonNode.class)
                    .timeout(Duration.ofMinutes(5))
                    .doOnSuccess(resp -> this.handlerSuccessNext(resp, resultSync))
                    .onErrorResume(err -> {
                        this.handlerException(err, resultSync);
                        return Mono.error(err);
                    });
        });
    }

    private void handlerException(Throwable err, ResultSync resultSync) {
        if (err instanceof ApiException) {
            resultSync.setMsg(((ApiException) err).getMsg());
            resultSync.setStatus(((ApiException) err).getStatus());
        } else {
            resultSync.setMsg(err.getMessage());
            resultSync.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        }

        resultSync.setElapsedTime(System.currentTimeMillis() - resultSync.getElapsedTime());

        log.error("Sync {} data error action {}, elapsedTime {}, msg: {}",
                resultSync.getTenantId(), resultSync.getAction(),
                resultSync.getElapsedTime(), resultSync.getMsg());

        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, resultSync);
    }

    private void handlerSuccessNext(JsonNode resp, ResultSync resultSync) {
        JsonNode countNode = resp.get("count");

        if (ObjectUtils.isEmpty(countNode)) {
            resultSync.setCount(resp.withArray("results").size());
        } else {
            resultSync.setCount(countNode.intValue());
        }

        resultSync.setElapsedTime(System.currentTimeMillis() - resultSync.getElapsedTime());

        log.info("sync {} data success, action {}, elapsedTime {}, count {}",
                resultSync.getTenantId(), resultSync.getAction(),
                resultSync.getElapsedTime(), resultSync.getCount());

        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, resultSync);
    }
}
